package ctn.batch;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import ctn.contant.MsgContant;
import ctn.contant.PlugContant;
import ctn.dao.inteface.CPAFunDao;
import ctn.dao.inteface.CPAInfoDao;
import ctn.dao.inteface.CTNCPALogDao;
import ctn.dao.inteface.CTNCPAOutDao;
import ctn.dao.inteface.CTNUserDao;
import ctn.hibernate.po.CtnCpaInfo;
import ctn.hibernate.po.CtnUser;
import ctn.hibernate.po.ScoreRecord;
import ctn.util.RuleUtil;

@Component
@Lazy(false)
public class RuleTask {

	@Autowired
	private CPAFunDao cpaFunDaoImpl;
	
	@Autowired
	private CTNUserDao userDao;
	
	@Autowired
	private CPAInfoDao cpaInfoDao;
	
	@Autowired
	private CTNCPALogDao logDao;
	
	@Autowired
	private CTNCPAOutDao outDao;
	
	@Autowired
	@Qualifier("filePro")
	private Properties prop;
	
	private Integer maxDelayDay;
	private Integer stageSize;
	private Long divideFailed;
	private Long maxLevel;
	private Long maxFailed;
	private int ruleBatchJobFrequency;
	private Long initLevel;
	private Long initFailed;
	private Integer awardDay;
	private Integer resetPlanEndDate;
	private String creater = MsgContant.RULE_CREATE_USER_NAME;
	private String logFunContent = MsgContant.RULE_RELEASE_FUN_LOG;
	private String logCpaLevelContent = MsgContant.RULE_RELEASE_PROJECT_LEVEL_LOG;
	private String logCpaFailedContent = MsgContant.RULE_RELEASE_PROJECT_FAILED_LOG;
	private ApplicationContext context;
	//log
	private Log log = LogFactory.getLog(RuleTask.class);
	/**
	 * initiation constants
	 */
	private void initiationConstant(){
		log.info("start to initiation the contants...");
		maxDelayDay = Integer.parseInt((String)prop.get(PlugContant.RULE_MAX_DELAY_DAY));
		stageSize =  Integer.parseInt((String)prop.get(PlugContant.RULE_MAX_STAGE_SIZE));
		divideFailed = Long.parseLong((String)prop.get(PlugContant.RULE_FAILED_LEVEL_DIVIDE));
		initLevel = Long.parseLong((String)prop.get(PlugContant.RULE_INITIATION_LEVEL_VALUE));
		initFailed = Long.parseLong((String)prop.get(PlugContant.RULE_INITIATION_FAILED_VALUE));
		awardDay =  Integer.parseInt((String)prop.get(PlugContant.RULE_AWARD_DAY));
		maxLevel = Long.parseLong((String)prop.get(PlugContant.RULE_MAX_LEVEL));
		maxFailed = Long.parseLong((String)prop.get(PlugContant.RULE_MAX_FAILED));
		ruleBatchJobFrequency = Integer.parseInt((String)prop.get(PlugContant.RULE_BATCH_JOB_FREQUENCY));
		resetPlanEndDate = Integer.parseInt((String)
								prop.get(PlugContant.RULE_RELEASE_PLAN_END_DATE_RESET_RATE));
		//default:rule batch job frequency is one day one time
		if(ruleBatchJobFrequency==0){
			ruleBatchJobFrequency = 1;
		}
		//default:reset plan end date not be 0
		if(resetPlanEndDate==0){
			resetPlanEndDate = 2;
		}
		//default:max delay day must be no less than stage size
		if(maxDelayDay < stageSize){
			maxDelayDay = stageSize;
		}
		//initiate the context
		context = new ClassPathXmlApplicationContext(PlugContant.SPRING_CONFIG_PATH);
		log.info("finish the initiation contants...");
	}
	
	@Scheduled(cron=" 1 0 0 * * ? ")
	public void check(){
		//first,initiation the constants
		initiationConstant();
		//second,get the project of status doing
		log.info("rule task start...");
		log.info("search the project info with status doing...");
		List<CtnCpaInfo> list = cpaInfoDao.getAllByStatus(PlugContant.STATUS_DOING);

		for (CtnCpaInfo ci : list) {
			
			log.info("process function with the moved status");
			//------------Status Moved--------------------------
			processUserScoreWithStatusMoved(ci);
			//--------------------------------------------------
			log.info("finish function with the moved status");
			
			log.info("process function with the doing status");
			//------------Status Doing--------------------------
			processUserScoreWithStatusDoing(ci);
			//--------------------------------------------------
			log.info("finish function with the doing status");
			
			log.info("process function with the complete status");
			//------------Status Complete-----------------------
			processUserScoreWithStatusComplete(ci);
			//--------------------------------------------------
			log.info("finish function with the complete status");
		
		}
		
		log.info("rule task is finish");
	}
	
	/**
	 * process level value and failed value of implement mission user 
	 * with the function in moved status  
	 * @param ci
	 */
	private void processUserScoreWithStatusMoved(CtnCpaInfo ci){
		long award = 0L;
		long userId = -1L;
		CtnUser u = null;
		List<Map<String, Object>> ms = cpaFunDaoImpl.getFunImplUserId(ci.getId(), PlugContant.FUN_STATUS_MOVED, null);
		for (Map<String, Object> m : ms) {
			userId = (Long)m.get(CPAFunDao.FUN_KEY_IMPL_USER_ID);
			u = userDao.getUserByID(userId);
			//get moved fun mission
			List<Map<String, Object>> funs = cpaFunDaoImpl.listFunInfoWithMovedStatus(
												ci.getId(), PlugContant.FUN_STATUS_MOVED, userId);
			for (Map<String, Object> map : funs) {
				//update the function status ready
				cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STATUS}, 
								new Object[]{PlugContant.FUN_STATUS_READY}, 
									(Long)map.get(CPAFunDao.FUN_KEY_FID));
				//update the function mission stage
				if((Long)map.get(CPAFunDao.FUN_KEY_STAGE)!=0L){
					cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STAGE}, 
							new Object[]{0L}, (Long)map.get(CPAFunDao.FUN_KEY_FID));
				}
				//award the be moved user
				award++;
			}
			//update the implement user level and failed value
			updateLevelandFailedOfUser(u, new ScoreRecord(
					getAwardLevel(u.getLevelCount(),award),-1L,true));
			award=0L;
		}
		//recover the status
		cpaFunDaoImpl.recoverStatus(ci.getId(), PlugContant.FUN_STATUS_MOVED);
	}
	
	/**
	 * process level value and failed value of implement mission user 
	 * with the function in doing status  
	 * @param ci
	 */
	private void processUserScoreWithStatusDoing(CtnCpaInfo ci){
		CtnUser u = null;
		int npDiffDay = 0;		//the differ day equal today - plan end date
		int stage = 0;			//there are 3 stage: 1 to -1, 2 to -1, 3 to -1
		long delayScore = 0L;
		long delayScoreStore = 0L;
		long failedScore = 0L;
		ScoreRecord sr = null;
		long userId = 0L;
		boolean isInit = false;
		//get over plan end date fun mission
		List<Map<String, Object>> ms = cpaFunDaoImpl.getFunImplUserId(ci.getId(), PlugContant.FUN_STATUS_DOING, new Date());
		for (Map<String, Object> m : ms) {
			userId = (Long)m.get(CPAFunDao.FUN_KEY_IMPL_USER_ID);
			u = userDao.getUserByID(userId);
			long userFailed = u.getFaildCount();
			long userLevel = u.getLevelCount();
			List<Map<String, Object>> funs = cpaFunDaoImpl.listFunInfoWithDoingStatus(
					ci.getId(), PlugContant.STATUS_DOING, new Date(),userId);
			for (Map<String, Object> cf : funs) {
				//update the function status ready
				cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STATUS},
									new Object[]{PlugContant.FUN_STATUS_READY}, 
										(Long)cf.get(CPAFunDao.FUN_KEY_FID));
				
				npDiffDay = (Integer)cf.get(CPAFunDao.FUN_KEY_N_P_DIFF);
				stage = (Integer)cf.get(CPAFunDao.FUN_KEY_STAGE);
				
				sr = RuleUtil.userLevelSubtract
						(stage, maxDelayDay, npDiffDay-ruleBatchJobFrequency, stageSize);
				delayScore += sr.getLevelValue();
				delayScoreStore += sr.getLevelValue();
				//if delay score over the divide failed value 
				//failed score add 1
				if(delayScoreStore >= divideFailed.longValue()){
					failedScore++;
					delayScoreStore = 0L;
				}
				
				//implement user over the dead line of delay
				//and failed score add 1
				//and release the function mission
				//and record the log and out function record
				if(sr.isOverDelayDeadLine()){
					failedScore += sr.getFailedValue();
					//update the function status wait
					cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STATUS,
									PROPERTY_NAME_STAGE,PROPERTY_NAME_PLAN_END_DATE}, 
							new Object[]{PlugContant.FUN_STATUS_WAIT,0L,setPlanEndDateAfterRelease()},
							(Long)cf.get(CPAFunDao.FUN_KEY_FID));
					//record log and out
					outRecord(TYPE_FUN, (Long)cf.get(CPAFunDao.FUN_KEY_FID), userId);
					logContent(getMessage(logFunContent,new Object[]{cf.get(CPAFunDao.FUN_KEY_FUN_NAME),u.getName()}));
				}else if (stage!=sr.getStage()) {	//update the stage
					cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STAGE}, 
							new Object[]{sr.getStage()}, (Long)cf.get(CPAFunDao.FUN_KEY_FID));
				}
				
				//if failed value over the max failed value
				//break to the loop 
				//and release the function mission about the implement user
				//and out of the project
				if((userLevel - delayScore <= 0) || 
						(userFailed + failedScore >= maxFailed)){
					cpaFunDaoImpl.updateFunctionByUserId(new String[]{PROPERTY_NAME_STATUS,
										PROPERTY_NAME_STAGE,PROPERTY_NAME_PLAN_END_DATE},
											new Object[]{PlugContant.FUN_STATUS_WAIT,0L,setPlanEndDateAfterRelease()}, 
												userId, ci.getId(),PlugContant.FUN_STATUS_DOING);
					isInit = true;
					//record log and out
					outRecord(TYPE_CPA, ci.getId(), userId);
					if (userLevel - delayScore <= 0) {
						logContent(getMessage(logCpaLevelContent,
								new Object[]{ci.getName(),u.getName()}));
					}else{
						logContent(getMessage(logCpaFailedContent, 
								new Object[]{ci.getName(),u.getName()}));
					}
					break;
				}
				
			}
			if (!isInit) {
				//update the implement user level and failed value
				if (delayScore==0L && failedScore != 0L) {
					updateLevelandFailedOfUser(u, new ScoreRecord(failedScore));
				}else{
					updateLevelandFailedOfUser(u, new ScoreRecord(delayScore, 
							failedScore, false));
				}
			}else{
				u.setLevelCount(initLevel);
				u.setFaildCount(initFailed);
				userDao.updateUser(u);
			}
			isInit = false;
			delayScore = 0L;
			failedScore = 0L;
		}
		//recover the status
		cpaFunDaoImpl.recoverStatus(ci.getId(), PlugContant.FUN_STATUS_DOING);
	}
	
	/**
	 * process level value and failed value of implement mission user 
	 * with the function in complete status  
	 * @param ci
	 */
	private void processUserScoreWithStatusComplete(CtnCpaInfo ci){
		long award = 0L;
		long userId = -1L;
		int differDay = 0;
		CtnUser u = null;
		int stage = 0;			//there are 3 stage: 1 to -1, 2 to -1, 3 to -1
		long delayScore = 0L;
		long delayScoreStore = 0L;
		long failedScore = 0L;
		ScoreRecord sr = null;
		boolean isInit = false;
		List<Map<String, Object>> ms = cpaFunDaoImpl.getFunImplUserId(ci.getId(), PlugContant.FUN_STATUS_COMPLETE, null);
		for (Map<String, Object> m : ms) {
			userId = (Long)m.get(CPAFunDao.FUN_KEY_IMPL_USER_ID);
			u = userDao.getUserByID(userId);
			long userFailed = u.getFaildCount();
			long userLevel = u.getLevelCount();
			//get complete fun mission
			List<Map<String, Object>> funs = cpaFunDaoImpl.listFunInfoWithCompleteStatus
												(ci.getId(), PlugContant.FUN_STATUS_COMPLETE,userId);
			for (Map<String, Object> map : funs) {
				
				//update the function status ready
				cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STATUS}, 
								new Object[]{PlugContant.FUN_STATUS_READY}, 
									(Long)map.get(CPAFunDao.FUN_KEY_FID));
				
				differDay = (Integer)map.get(CPAFunDao.FUN_KEY_C_P_DIFF);
				if (differDay < 0) {
					int beforeDay = new BigDecimal(differDay).abs().intValue();
					award = beforeDay >= awardDay?++award:award;
				}else if(differDay > 0){
					stage = (Integer)map.get(CPAFunDao.FUN_KEY_STAGE);
					sr = RuleUtil.userLevelSubtract
							(stage, maxDelayDay, differDay, stageSize);
					
					delayScore += sr.getLevelValue();
					delayScoreStore += sr.getLevelValue();
					//if delay score over the divide failed value 
					//failed score add 1
					if(delayScoreStore >= divideFailed.longValue()){
						failedScore++;
						delayScoreStore = 0L;
					}
					
					//update the stage
					if (stage!=sr.getStage()) {
						cpaFunDaoImpl.updateFunctionById(new String[]{PROPERTY_NAME_STAGE}, new Object[]{sr.getStage()}, 
										(Long)map.get(CPAFunDao.FUN_KEY_FID));
					}
					
					//if failed value over the max failed value or level value over the max level value
					//break to the loop 
					//and release the function mission about the implement user
					//and out of the project
					if((userLevel - delayScore <= 0) || 
							(userFailed + failedScore >= maxFailed)){
						cpaFunDaoImpl.updateFunctionByUserId(new String[]{PROPERTY_NAME_STATUS,
									PROPERTY_NAME_STAGE,PROPERTY_NAME_PLAN_END_DATE},
										new Object[]{PlugContant.FUN_STATUS_WAIT,0L,setPlanEndDateAfterRelease()}, 
											userId, ci.getId(),PlugContant.FUN_STATUS_COMPLETE);
						isInit = true;
						//record log and out
						outRecord(TYPE_CPA, ci.getId(), userId);
						if (userLevel - delayScore <= 0) {
							logContent(getMessage(logCpaLevelContent,
									new Object[]{ci.getName(),u.getName()}));
						}else{
							logContent(getMessage(logCpaFailedContent, 
									new Object[]{ci.getName(),u.getName()}));
						}
						break;
					}
				}
			}
			if (!isInit) {
				//update the implement user level and failed value
				if(award == delayScore){
					updateLevelandFailedOfUser(u, new ScoreRecord(failedScore));
				}else if(award > delayScore){
					updateLevelandFailedOfUser(u, new ScoreRecord(
							getAwardLevel(u.getLevelCount(),award - delayScore),failedScore,true));
				}else{
					updateLevelandFailedOfUser(u, new ScoreRecord(delayScore - award,
							failedScore,false));
				}
			}else{
				u.setLevelCount(initLevel);
				u.setFaildCount(initFailed);
				userDao.updateUser(u);
			}
			//initiation constant
			isInit = false;
			delayScore = 0L;
			award = 0L;
			failedScore = 0L;
		}
		//recover the status
		cpaFunDaoImpl.recoverStatus(ci.getId(), PlugContant.FUN_STATUS_COMPLETE);
	}
	
	/**
	 * calculate the award level
	 * @param level
	 * @param award
	 * @return
	 */
	private long getAwardLevel(long level,long award){
		if (level + award > maxLevel) {
			level = maxLevel;
		}else{
			level += award; 
		}
		return level;
	}
	
	/**
	 * update the level value and the failed value of implement function user
	 * @param u
	 * @param sr
	 */
	private void updateLevelandFailedOfUser(CtnUser u,ScoreRecord sr){
		long failed = sr.getFailedValue();
		if (sr.isFailed()) {
			u.setFaildCount(failed+u.getFaildCount());
			userDao.updateUser(u);
			return;
		}
		long level = sr.getLevelValue();
		if(sr.isAward()){
			u.setLevelCount(level);
			if (failed != -1L) {
				u.setFaildCount(failed+u.getFaildCount());
			}
			userDao.updateUser(u);
			return;
		}
		if(level != 0){
			long levelCount = u.getLevelCount();
			long result = levelCount - level;
			u.setLevelCount(result);
			u.setFaildCount(u.getFaildCount()+failed);
			userDao.updateUser(u);
		}
	}
	
	/**
	 * when need to release the function mission,
	 * plan end  date must be reseted
	 * @param psDiff
	 * @return
	 */
	private Date setPlanEndDateAfterRelease(){
		Date date = new Date(System.currentTimeMillis()+1000*60*60*24*resetPlanEndDate);
		return date;
	}
	
	/**
	 * record the project out or function out
	 * @param type
	 * @param id
	 * @param userId
	 * @param createUser
	 * @return
	 */
	private boolean outRecord(String type,Long id,Long userId){
		Object[] values = null;
		if (TYPE_FUN.equals(type)) {
			values = new Object[]{type,id,null,getMessage(creater),id,new Date()};
		}else if (TYPE_CPA.equals(type)){
			values = new Object[]{type,null,id,getMessage(creater),id,new Date()};
		}
		return outDao.saveOutBySql(outProperties, values);
	}
	
	/**
	 * record the log
	 * @param content
	 * @param user
	 * @return
	 */
	private boolean logContent(String content){
		Object[] values = new Object[]{PROPERTY_NAME_BATCH,content,new Date(),getMessage(creater)};
		return logDao.saveLogBySql(logProperties, values);
	}
	
	/**
	 * return the message
	 * @param key
	 * @param values
	 * @return
	 */
	private String getMessage(String key,Object[] values){
		return context.getMessage(key, values, null);
	}
	
	/**
	 * return the message
	 * @param key
	 * @return
	 */
	private String getMessage(String key){
		return context.getMessage(key,null,null);
	}
	
	private String[] logProperties = new String[]{"log_type","log_content","log_date","log_user"};
	private String[] outProperties = new String[]{"out_type","out_fun_id","out_cpa_id","create_user_name","out_user_id","create_date"};
	private final static String TYPE_FUN = "FUN";
	private final static String TYPE_CPA = "FUN";
	private final static String PROPERTY_NAME_STAGE = "stage";
	private final static String PROPERTY_NAME_BATCH = "batch";
	private final static String PROPERTY_NAME_STATUS = "status";
	private final static String PROPERTY_NAME_PLAN_END_DATE = "plan_end_date";
}
